package model

import (
	"database/sql"
	"fmt"
	"strings"

	"github.com/tal-tech/go-zero/core/stores/builder"
	"github.com/tal-tech/go-zero/core/stores/cache"
	"github.com/tal-tech/go-zero/core/stores/sqlc"
	"github.com/tal-tech/go-zero/core/stores/sqlx"
	"github.com/tal-tech/go-zero/core/stringx"
)

var (
	sstWordsFieldNames          = builder.RawFieldNames(&SstWords{})
	sstWordsRows                = strings.Join(sstWordsFieldNames, ",")
	sstWordsRowsExpectAutoSet   = strings.Join(stringx.Remove(sstWordsFieldNames, "`id`", "`create_time`", "`update_time`"), ",")
	sstWordsRowsWithPlaceHolder = strings.Join(stringx.Remove(sstWordsFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?"

	cacheSstWordsIdPrefix      = "cache:sstWords:id:"
	cacheSstWordsTitlePrefix   = "cache:sstWords:title:"
	cacheSstWordsAllListPrefix = "cache:sstWords:alllist"
)

type (
	SstWordsModel interface {
		Insert(data *SstWords) (sql.Result, error)
		FindOne(id int64) (*SstWords, error)
		FindOneByTitle(title string) (*SstWords, error)
		FindAllList() ([]*SstWords, error)
		Update(data *SstWords) error
		Delete(id int64) error
	}

	defaultSstWordsModel struct {
		sqlc.CachedConn
		table string
	}

	SstWords struct {
		Id    int64  `db:"id"`
		Title string `db:"title"` // word title
	}
)

func NewSstWordsModel(conn sqlx.SqlConn, c cache.CacheConf) SstWordsModel {
	return &defaultSstWordsModel{
		CachedConn: sqlc.NewConn(conn, c),
		table:      "`sst_words`",
	}
}

func (m *defaultSstWordsModel) Insert(data *SstWords) (sql.Result, error) {
	sstWordsIdKey := fmt.Sprintf("%s%v", cacheSstWordsIdPrefix, data.Id)
	sstWordsTitleKey := fmt.Sprintf("%s%v", cacheSstWordsTitlePrefix, data.Title)
	ret, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
		query := fmt.Sprintf("insert into %s (%s) values (?)", m.table, sstWordsRowsExpectAutoSet)
		return conn.Exec(query, data.Title)
	}, sstWordsIdKey, sstWordsTitleKey)
	return ret, err
}

func (m *defaultSstWordsModel) FindOne(id int64) (*SstWords, error) {
	sstWordsIdKey := fmt.Sprintf("%s%v", cacheSstWordsIdPrefix, id)
	var resp SstWords
	err := m.QueryRow(&resp, sstWordsIdKey, func(conn sqlx.SqlConn, v interface{}) error {
		query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", sstWordsRows, m.table)
		return conn.QueryRow(v, query, id)
	})
	switch err {
	case nil:
		return &resp, nil
	case sqlc.ErrNotFound:
		return nil, ErrNotFound
	default:
		return nil, err
	}
}

func (m *defaultSstWordsModel) FindOneByTitle(title string) (*SstWords, error) {
	sstWordsTitleKey := fmt.Sprintf("%s%v", cacheSstWordsTitlePrefix, title)
	var resp SstWords
	err := m.QueryRowIndex(&resp, sstWordsTitleKey, m.formatPrimary, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
		query := fmt.Sprintf("select %s from %s where `title` = ? limit 1", sstWordsRows, m.table)
		if err := conn.QueryRow(&resp, query, title); err != nil {
			return nil, err
		}
		return resp.Id, nil
	}, m.queryPrimary)
	switch err {
	case nil:
		return &resp, nil
	case sqlc.ErrNotFound:
		return nil, ErrNotFound
	default:
		return nil, err
	}
}

func (m *defaultSstWordsModel) FindAllList() ([]*SstWords, error) {

	sstWordsAllListKey := fmt.Sprintf("%s%v", cacheSstWordsAllListPrefix, "sstwords_all_list")
	var resp []*SstWords
	m.GetCache(sstWordsAllListKey, &resp)
	if len(resp) > 0 {
		return resp, nil
	}
	query := fmt.Sprintf("select %s from %s where 1=1", sstWordsRows, m.table)
	err := m.QueryRowsNoCache(&resp, query)
	switch err {
	case nil:
		m.SetCache(sstWordsAllListKey, &resp)
		return resp, nil
	case sqlc.ErrNotFound:
		return nil, ErrNotFound
	default:
		return nil, err
	}
}

func (m *defaultSstWordsModel) Update(data *SstWords) error {
	sstWordsIdKey := fmt.Sprintf("%s%v", cacheSstWordsIdPrefix, data.Id)
	sstWordsTitleKey := fmt.Sprintf("%s%v", cacheSstWordsTitlePrefix, data.Title)
	_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
		query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, sstWordsRowsWithPlaceHolder)
		return conn.Exec(query, data.Title, data.Id)
	}, sstWordsIdKey, sstWordsTitleKey)
	return err
}

func (m *defaultSstWordsModel) Delete(id int64) error {
	data, err := m.FindOne(id)
	if err != nil {
		return err
	}

	sstWordsIdKey := fmt.Sprintf("%s%v", cacheSstWordsIdPrefix, id)
	sstWordsTitleKey := fmt.Sprintf("%s%v", cacheSstWordsTitlePrefix, data.Title)
	_, err = m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
		query := fmt.Sprintf("delete from %s where `id` = ?", m.table)
		return conn.Exec(query, id)
	}, sstWordsIdKey, sstWordsTitleKey)
	return err
}

func (m *defaultSstWordsModel) formatPrimary(primary interface{}) string {
	return fmt.Sprintf("%s%v", cacheSstWordsIdPrefix, primary)
}

func (m *defaultSstWordsModel) queryPrimary(conn sqlx.SqlConn, v, primary interface{}) error {
	query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", sstWordsRows, m.table)
	return conn.QueryRow(v, query, primary)
}
